﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace SpectrEngine.Framework.Collision
{
    public sealed class QuadTreeNode
    {
        #region Fields

        private BoundingRect bounds;

        public BoundingRect Bounds
        {
            get { return bounds; }
        }

        #endregion

        #region Local Variables

        private int maxNodes;
        private List<CollidableGameNode> nodes;

        private bool partitioned;

        private QuadTreeNode topLeftNode;
        private QuadTreeNode topRightNode;
        private QuadTreeNode bottomLeftNode;
        private QuadTreeNode bottomRightNode;

        private QuadTreeNode parentNode;

        #endregion

        #region Event Handlers

        void node_Translated(object sender, EventArgs e)
        {
            CollidableGameNode node = (CollidableGameNode)sender;

            if (nodes.Contains(node))
            {
                int i = nodes.IndexOf(node);

                if (!PushDown(i))
                {
                    if (parentNode != null)
                    {
                        PushUp(i);
                    }
                    else if (!bounds.Contains(node.Bounds))
                    {
                        //Not implemented yet, resize the world
                        throw new NotImplementedException();
                        //If you get thrown here, it means an actor in the tree has outside the world bounds
                    }
                }
            }
            else
            {
                node.Translated -= new EventHandler(node_Translated);
            }
        }


        void node_Disposed(object sender, EventArgs e)
        {
            RemoveItem((CollidableGameNode)sender);
        }

        #endregion

        #region Constructors

        public QuadTreeNode(QuadTreeNode parentNode, BoundingRect bounds, int maxNodes)
        {
            this.parentNode = parentNode; ;
            this.bounds = bounds;
            this.maxNodes = maxNodes;

            partitioned = false;

            nodes = new List<CollidableGameNode>();
        }

        #endregion

        #region Query Methods

        public void GetNodes(Vector2 point, ref List<CollidableGameNode> nodesFound)
        {
            if (bounds.Contains(point))
            {
                foreach (CollidableGameNode node in nodes)
                {
                    if (node.Bounds.Contains(point))
                    {
                        nodesFound.Add(node);
                    }
                }

                if (partitioned)
                {
                    topLeftNode.GetNodes(point, ref nodesFound);
                    topRightNode.GetNodes(point, ref nodesFound);
                    bottomLeftNode.GetNodes(point, ref nodesFound);
                    bottomRightNode.GetNodes(point, ref nodesFound);
                }
            }
        }

        public void GetNodes(BoundingRect otherBounds, ref List<CollidableGameNode> nodesFound)
        {
            if (bounds.Contains(otherBounds))
            {
                foreach (CollidableGameNode node in nodes)
                {
                    if (node.Bounds.Intersects(otherBounds))
                    {
                        nodesFound.Add(node);
                    }
                }

                if (partitioned)
                {
                    topLeftNode.GetNodes(otherBounds, ref nodesFound);
                    topRightNode.GetNodes(otherBounds, ref nodesFound);
                    bottomLeftNode.GetNodes(otherBounds, ref nodesFound);
                    bottomRightNode.GetNodes(otherBounds, ref nodesFound);
                }
            }
        }

        public void GetAllNodes(ref List<CollidableGameNode> nodesFound)
        {
            nodesFound.AddRange(nodes);

            if (partitioned)
            {
                topLeftNode.GetAllNodes(ref nodesFound);
                topRightNode.GetAllNodes(ref nodesFound);
                bottomLeftNode.GetAllNodes(ref nodesFound);
                bottomRightNode.GetAllNodes(ref nodesFound);
            }
        }

        public QuadTreeNode FindNode(CollidableGameNode node)
        {
            if (nodes.Contains(node))
            {
                return this;
            }

            if (partitioned)
            {
                QuadTreeNode foundNode = null;

                if (topLeftNode.Bounds.Contains(node.Bounds))
                {
                    foundNode = topLeftNode.FindNode(node);
                }
                else if (topRightNode.Bounds.Contains(node.Bounds))
                {
                    foundNode = topRightNode.FindNode(node);
                }
                else if (bottomLeftNode.Bounds.Contains(node.Bounds))
                {
                    foundNode = bottomLeftNode.FindNode(node);
                }
                else if (bottomRightNode.Bounds.Contains(node.Bounds))
                {
                    foundNode = bottomRightNode.FindNode(node);
                }

                return foundNode;
            }

            return null;
        }

        #endregion

        #region Insertion Methods

        public void Add(CollidableGameNode node)
        {
            if (!addtoChildren(node))
            {
                node.Translated += new EventHandler(node_Translated);
                node.Disposed += new EventHandler(node_Disposed);
                nodes.Add(node);

                if (!partitioned && nodes.Count > maxNodes)
                {
                    partition();
                }
            }
        }

        public bool PushDown(int i)
        {
            if (addtoChildren(nodes[i]))
            {
                RemoveItem(i);
                return true;
            }

            return false;
        }

        public void PushUp(int i)
        {
            CollidableGameNode node = nodes[i];

            RemoveItem(i);

            parentNode.Add(node);
        }

        #endregion

        #region Destruction

        public void Dispose()
        {
            if (partitioned)
            {
                topLeftNode.Dispose();
                topRightNode.Dispose();
                bottomLeftNode.Dispose();
                bottomRightNode.Dispose();

                topLeftNode = null;
                topRightNode = null;
                bottomLeftNode = null;
                bottomRightNode = null;
            }

            while (nodes.Count > 0)
            {
                RemoveItem(0);
            }
        }

        public void RemoveItem(CollidableGameNode node)
        {
            if (nodes.Contains(node))
            {
                node.Translated -= new EventHandler(node_Translated);
                node.Disposed -= new EventHandler(node_Disposed);
                nodes.Remove(node);
            }
        }

        public void RemoveItem(int i)
        {
            if (i < nodes.Count)
            {
                nodes[i].Translated -= new EventHandler(node_Translated);
                nodes[i].Disposed -= new EventHandler(node_Disposed);
                nodes.RemoveAt(i);
            }
        }

        #endregion

        #region Helper Methods

        private bool addtoChildren(CollidableGameNode node)
        {
            if (!partitioned)
            {
                return false;
            }

            if (topLeftNode.Bounds.Contains(node.Bounds))
            {
                topLeftNode.Add(node);
            }
            else if (topRightNode.bounds.Contains(node.Bounds))
            {
                topRightNode.Add(node);
            }
            else if (bottomLeftNode.bounds.Contains(node.Bounds))
            {
                bottomLeftNode.Add(node);
            }
            else if (bottomRightNode.bounds.Contains(node.Bounds))
            {
                bottomRightNode.Add(node);
            }
            else
            {
                return false;
            }

            return true;
        }

        private void partition()
        {
            Vector2 midPoint = bounds.Center;

            topLeftNode = new QuadTreeNode(this, new BoundingRect(bounds.TopLeft, midPoint), maxNodes);
            topRightNode = new QuadTreeNode(this, new BoundingRect(new Vector2(midPoint.X, bounds.Top), new Vector2(bounds.Right, midPoint.Y)), maxNodes);
            bottomLeftNode = new QuadTreeNode(this, new BoundingRect(new Vector2(bounds.Left, midPoint.Y), new Vector2(midPoint.X, bounds.Bottom)), maxNodes);
            bottomRightNode = new QuadTreeNode(this, new BoundingRect(midPoint, bounds.BottomRight), maxNodes);

            partitioned = true;

            int i = 0;
            while (i < nodes.Count)
            {
                if (!PushDown(i))
                {
                    i++;
                }
            }
        }

        #endregion
    }
}
